﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Threading;

namespace RemoteObject
{
    public class Common
    {
        public class BlockBufferInfo
        {
            public Byte[] buffer;
            public int bufferStartPos;
            public int dataSize;
            public int dataSizeUncompressed;
            public Boolean dataCompressed;
            public Boolean isBufferEmpty;

            public BlockBufferInfo()
            {
                bufferStartPos = 0;
                dataSize = 0;
                dataSizeUncompressed = 0;
                dataCompressed = false;
                isBufferEmpty = true;
            }

            public void CloneInfoFrom( BlockBufferInfo bbiFrom )
            {
                dataSize = bbiFrom.dataSize;
                dataSizeUncompressed = bbiFrom.dataSizeUncompressed;
                dataCompressed = bbiFrom.dataCompressed;
            }
        }

        public static Boolean RunAsServer = false;
        public static Boolean ApplicationQuit = false;

        public static DateTime UnkownDateTime = new DateTime(1900,1,1);

        //public static BinaryFormatter bf = new BinaryFormatter();
        public static string LocalIPAddress = Dns.GetHostByName(Dns.GetHostName()).AddressList[0].ToString();

        public static string RemoteServerUrl = "ServiceURLTcp";
        public static string CloudServerURL = "tcp://127.0.0.1:10001/RemoteObject.RemoteObjectDownloader";
        public static string IndexServerURL = "tcp://127.0.0.1:10001/RemoteObject.IndexServer";

        public const int BlockSize = 1024 * 512;
        public static BlockBufferInfo endingBlockBuffer = new BlockBufferInfo();

        public static string CloudPath = @"C:\c\";
        public static string LocalPath = @"C:\c\";
        public static string LocalPathOfPendingJob = @"~PendingJob~\";
        public static string DownloadNetFileExtension = ".~~Net~~";
        public static string UploadNetFileExtension = ".~~UploadNet~~";
        public static string FileDisInfoPath = @"c:\c\~FileDisInfo~\";
        public static string FileDisInfoFileExt = @".~~DisInfo~~";

        public static int MAX_BLOCKS = 8;

        public enum Status
        {
            Working,
            Pause,
            Completed
        }

        private const int BlockBufferSize = 200;
        private static List<BlockBufferInfo> freeBlockBufferInfoList = null;

        public static void initFreeBlockBuffer()
        {
            if (freeBlockBufferInfoList == null)
            {
                freeBlockBufferInfoList = new List<BlockBufferInfo>();
                for (int i = 0; i < BlockBufferSize; i++)
                {
                    BlockBufferInfo bbi = new BlockBufferInfo();
                    bbi.dataSize = 0;
                    bbi.buffer = new Byte[BlockSize];
                    bbi.bufferStartPos = 0;
                    bbi.dataCompressed = false;
                    freeBlockBufferInfoList.Add(bbi);
                }
            }
        }

        public static BlockBufferInfo getFreeBlock(int dataSize)
        {
            initFreeBlockBuffer();
            lock (endingBlockBuffer)
            {
                if (freeBlockBufferInfoList.Count == 0)
                    return null;

                BlockBufferInfo freeBBI = freeBlockBufferInfoList[0];
                freeBBI.dataSize = dataSize;
                freeBBI.isBufferEmpty = true;
                freeBlockBufferInfoList.RemoveAt(0);

                return freeBBI;
            }
        }

        public static BlockBufferInfo getFreeBlockOnWait(int dataSize)
        {
            BlockBufferInfo bbiToSend = Common.getFreeBlock(dataSize);
            while (bbiToSend == null)
            {
                //no free block then wait for more block to release
                Thread.Sleep(100);
                bbiToSend = Common.getFreeBlock(dataSize);
            }
            return bbiToSend;
        }

        public static int getFreeBlockNum()
        {
            initFreeBlockBuffer();
            return freeBlockBufferInfoList.Count;
        }

        public static void releaseBlock( BlockBufferInfo bbi )
        {
            if (bbi == null || bbi == endingBlockBuffer )
                return;

            lock (endingBlockBuffer)
            {
                freeBlockBufferInfoList.Insert(0, bbi);
            }
        }

        //other ultity api
        public static String GetMyIPAdress()
        {
            IPHostEntry ipHost   =  Dns.Resolve(Dns.GetHostName());
            IPAddress   ipAddr   =  ipHost.AddressList[0];
            return   ipAddr.ToString();
        }

        public static RemoteObject.Site localSite = new RemoteObject.Site();
        public static RemoteObject.IndexServer IndexServerObject;
        public static long indexServerChannelID;

        //get max block number
        public static int GetMaxBlockNumber( int fileLength )
        {
            int OneMega = 1024 * 1024;
            int HowManyBlocks = 2;
            if (fileLength > 100 * OneMega)
            {
                HowManyBlocks = Common.MAX_BLOCKS;
            }
            else if (fileLength > 20 * OneMega)
            {
                HowManyBlocks = 4;
            }
            else if (fileLength > 1 * OneMega)
            {
                HowManyBlocks = 3;
            }
            else if (fileLength < 50 * 1024)
            {
                HowManyBlocks = 1;
            }
            return HowManyBlocks;
        }

        public class IndexServerPartner
        {
            static Thread threadHearbeat = new Thread(new ThreadStart(SiteHeartBeatProc));
            static Thread threadTaskHandler = new Thread(new ThreadStart(RemoteTaskActionProc));
            static Boolean indexServerDown = false;
            static RemoteObject.TalkChannel talkChannel;

            //ConnectToIndexServer
            public static Boolean ConnectToIndexServer()
            {
                try
                {
                    IndexServerObject = (RemoteObject.IndexServer)Activator.GetObject(typeof(RemoteObject.IndexServer), Common.IndexServerURL);
                    indexServerChannelID = IndexServerObject.ApplyTalkChannelID();
                    talkChannel = IndexServerObject.StartTalkToIndexServer(indexServerChannelID, Common.localSite);
                    indexServerDown = false;
                    Console.WriteLine(" --- Connected to IndexServer ---");
                }
                catch
                {
                    IndexServerObject = null;
                    indexServerDown = true;
                }
                return IndexServerObject != null;
            }

            public static Boolean DisconnectToIndexServer()
            {
                try
                {
                    IndexServerObject.StopTalkToIndexServer(talkChannel);
                    IndexServerObject = null;
                    threadHearbeat = null;
                    return true;
                }
                catch
                {
                    return false;
                }
            }

            //start heart beat thread.
            public static void RunHeartBeatThread()
            {
                threadHearbeat.Start();
                threadTaskHandler.Start();
            }
            
            //Site heartbeat proc
            private static AutoResetEvent TriggerHearBeatEvent = new AutoResetEvent(true);
            private static void SiteHeartBeatProc()
            {
                int timeout = 1000 * 3;
                while (!Common.ApplicationQuit)
                {
                    Boolean bIdle = (false == TriggerHearBeatEvent.WaitOne(timeout));

                    if (indexServerDown)
                    {
                        if (!ConnectToIndexServer())
                        {
                            timeout = 1000 * 5;
                            continue;
                        }
                        timeout = 1000 * 3;
                    }

                    try
                    {
                        List<RemoteTaskBase> myDoneTasksForUpdate = GetMyActionDoneTaskList();
                        List<RemoteTaskBase> tasksNeedMyAction, doneTasksIRequested;
                        if (-1 == IndexServerObject.rmHeartBeatFromSite( indexServerChannelID, out tasksNeedMyAction, out doneTasksIRequested, myDoneTasksForUpdate))
                            continue;

                        if (myDoneTasksForUpdate != null)
                        {
                            lock (myActionDoneTasks)
                            {
                                myActionDoneTasks.RemoveRange(0, myDoneTasksForUpdate.Count);
                            }
                        }

                        if (tasksNeedMyAction != null)
                        {
                            AddToActionTaskList(tasksNeedMyAction);
                        }

                        if (doneTasksIRequested != null)
                        {
                            foreach(RemoteTaskBase st in doneTasksIRequested )
                            {
                                Console.WriteLine(" - Remote Done - " + st.TaskDesc() + " " + myActionTasks.Count + " " + myActionDoneTasks.Count );
                            }
                        }

                        ProcessPendingJob();
                    }
                    catch
                    {
                        Console.WriteLine(" --- Disconnected from IndexServer ---");
                        indexServerDown = true;
                    }
                }
            }

            static List<RemoteTaskBase> myActionTasks = new List<RemoteTaskBase>();
            static List<RemoteTaskBase> myActionDoneTasks = new List<RemoteTaskBase>();

            private static void AddToActionTaskList(List<RemoteTaskBase> newTasks)
            {
                lock (myActionTasks)
                {
                    foreach (RemoteTaskBase st in newTasks)
                    {
                        myActionTasks.Add(st);
                    }
                }
            }

            private static void AddToActionDoneTaskList(RemoteTaskBase doneTask)
            {
                lock (myActionDoneTasks)
                {
                    myActionDoneTasks.Add(doneTask);
                    Console.WriteLine(" - Local Done - "  + doneTask.TaskDesc() );
                    TriggerHearBeatEvent.Set();
                }
            }

            private static List<RemoteTaskBase> GetMyActionDoneTaskList()
            {
                lock (myActionDoneTasks)
                {
                    List<RemoteTaskBase> doneTasks = new List<RemoteTaskBase>();
                    foreach (RemoteTaskBase rt in myActionDoneTasks)
                    {
                        doneTasks.Add(rt);
                    }
                    return doneTasks.Count == 0 ? null : doneTasks;
                }
            }


            class RemoteTaskHandler
            {
                RemoteTaskBase task;
                public RemoteTaskHandler(RemoteTaskBase _task)
                {
                    task = _task;
                }
                public void StartProcess()
                {
                    try{_StartProcess();}catch{}
                    AddToActionDoneTaskList(task);
                }

                //The procedure for handling the remote tasks 
                void _StartProcess()
                {
                    switch (task.taskType)
                    {
                        case RemoteTaskType.Unknown:
                            break;
                        case RemoteTaskType.FileExchange:
                            break;
                        case RemoteTaskType.FileDelete:
                            {
                                RTaskFileDelete stDel = (RTaskFileDelete)task;
                                String fn = stDel.fileName;
                                File.Delete(Common.LocalPath + fn);
                                File.Delete(Common.LocalPath + fn + Common.DownloadNetFileExtension);
                                File.Delete(Common.LocalPath + fn + Common.UploadNetFileExtension);
                                RemoveFileFromCloud(fn);
                                task.taskStatus = RemoteTaskStatus.Done;
                            }
                            break;
                        default:
                            break;
                    }
                }
            }

            //The procedure for handling the remote tasks 
            private static void RemoteTaskActionProc()
            {
                while (!Common.ApplicationQuit)
                {
                    int nTaskCount = myActionTasks.Count;
                    if (nTaskCount == 0)
                    {
                        Thread.Sleep(1000);
                        continue;
                    }
                    try
                    {
                        for (int i = 0; i < nTaskCount; i++)
                        {
                            RemoteTaskBase task = myActionTasks[i];
                            (new Thread(new ThreadStart((new RemoteTaskHandler(task)).StartProcess))).Start();
                        }
                        lock (myActionTasks)
                        {
                            for (int i = 0; i < nTaskCount; i++)
                            {
                                myActionTasks.RemoveAt(0);
                            }
                        }
                    }
                    catch (Exception e)
                    {
                    }
                }
            }

            //Add file index.
            public static int AddFileToCloud(String fnLocal)
            {
                if (!File.Exists(fnLocal))
                    return -1;

                FileInfo fileInfo  = new FileInfo(fnLocal);
                FileIntegrity fInt = new FileIntegrity();
                fInt.totalFileSize = fileInfo.Length;

                try
                {
                    return IndexServerObject.UpdateFileDisInfo(indexServerChannelID, fnLocal.Substring(fnLocal.LastIndexOf('\\') + 1), fInt, null);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.ToString());
                    return -1;
                }
            }

            //Remove file index.
            public static int RemoveFileFromCloud(String fnLocal)
            {
                FileIntegrity fInt = new FileIntegrity();
                fInt.totalFileSize = -1; // -1 mean remove the local file dis info from index server

                try
                {
                    String fn = fnLocal.Substring(fnLocal.LastIndexOf('\\') + 1);
                    return IndexServerObject.UpdateFileDisInfo(indexServerChannelID, fn, fInt, null);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.ToString());
                    return -1;
                }
            }

            //Update file dis info to index server.
            static String pendingFileExt = ".~pending~";
            public static int AddPendingJobForUpdateFileDisInfo( String fn, FileIntegrity fInt, Site fileLocatedSite )
            {
                //index server down, save pending job to disk for later updating
                try
                {
                    lock (Common.LocalPathOfPendingJob)
                    {
                        PendingInfo pi = new PendingInfo();
                        pi.fInt = fInt;
                        pi.id = Guid.NewGuid();
                        pi.fileLocatedSite = fileLocatedSite;

                        FileStream fs = new FileStream(Common.LocalPath + Common.LocalPathOfPendingJob + fn + pendingFileExt, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
                        fs.SetLength(0);
                        (new BinaryFormatter()).Serialize(fs, pi);
                        fs.Flush();
                        fs.Close();
                    }
                }
                catch (Exception e2)
                {
                    return -1;
                }
                return 1;
            }

            [Serializable]
            class PendingInfo
            {
                public FileIntegrity fInt;
                public Guid id;
                public Site fileLocatedSite;
            }

            private static void ProcessPendingJob()
            {
                DirectoryInfo di = new DirectoryInfo(Common.LocalPath + Common.LocalPathOfPendingJob);
                FileInfo[] files = di.GetFiles("*" + pendingFileExt );

                BinaryFormatter bf = new BinaryFormatter();
                foreach (FileInfo fi in files)
                {
                    if (fi.Length > 10)
                    {
                        try
                        {
                            PendingInfo pi;
                            Guid currentID;
                            lock (Common.LocalPathOfPendingJob)
                            {
                                FileStream fs = new FileStream(fi.FullName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
                                pi = bf.Deserialize(fs) as PendingInfo;
                                currentID = pi.id;
                                fs.Close();
                            }

                            IndexServerObject.UpdateFileDisInfo(indexServerChannelID, fi.Name.Replace(pendingFileExt, ""), pi.fInt, pi.fileLocatedSite );

                            lock (Common.LocalPathOfPendingJob)
                            {
                                FileStream fs = new FileStream(fi.FullName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
                                pi = bf.Deserialize(fs) as PendingInfo;
                                fs.Close();
                                if (currentID == pi.id)
                                {
                                    File.Delete(fi.FullName);
                                }
                            }

                        }
                        catch (Exception e)
                        {
                        }
                    }
                }
            }
        }
    }
    /*
    class tFileIndex
    {
        //pk
        long id;

        String fileName;
        String desc; //

        long fileSize;
    }

    class tSite
    {
        //pk
        long id;

        String ID;
        String IP;
        String Name;
        String Desc; //
    }

    class tFileDisInfoItem
    {
        //pk
        long id;

        long siteID;

        //binary colum
        FileIntegrity fddi;
                
    }
     * */
}
